package com.pocket.insight.controller.dto;

import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import org.apache.commons.collections4.CollectionUtils;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author shuize
 * @create 2024/12/4
 */
@Data
@Schema(description = "路径表节点")
public class PathTableNode implements Serializable {

    @Schema(description = "路径类型：path-数据库，table-表")
    private PathType pathType;

    @Schema(description = "路径名称")
    private String name;

    @Schema(description = "路径描述")
    private String remarks;

    @Schema(description = "路径类型：TABLE")
    private TableType tableType;

    private List<PathTableNode> children;

    @JsonIgnore
    private Map<String, PathTableNode> nameToChild = new HashMap<>();

    private Boolean asyncChildren;

    public enum PathType {
        path,
        table;
    }

    @Getter
    public enum TableType {
        VIEW("VIEW"),
        TABLE("TABLE"),
        FOREIGN_TABLE("FOREIGN TABLE"),
        MATERIALIZED_VIEW("MATERIALIZED VIEW"),
        ;

        private String typeName;

        TableType(String typeName) {
            this.typeName = typeName;
        }

        public static TableType fromTypeName(String typeName) {
            for (TableType type : TableType.values()) {
                if (type.getTypeName().equalsIgnoreCase(typeName)) {
                    return type;
                }
            }
            return null;
        }
    }

    public void addChild(PathTableNode child) {
        if (this.children == null) {
            this.children = new ArrayList<>();
        }
        this.children.add(child);
        this.nameToChild.put(child.getName(), child);
    }

    public static PathTableNode createTableNode(String name) {
        PathTableNode node = new PathTableNode();
        node.setPathType(PathType.table);
        node.setName(name);
        node.setTableType(TableType.TABLE);
        return node;
    }

    public static PathTableNode createPathNode(String name) {
        PathTableNode toRet = new PathTableNode();
        toRet.setPathType(PathType.path);
        toRet.setName(name);
        toRet.initChildrenAndMapping(new ArrayList<>());
        return toRet;
    }


    public static List<PathTableNode> pathsToTree(List<List<Object>> paths) {
        List<PathTableNode> roots = new ArrayList<>();
        Map<String, PathTableNode> nameToRoot = new HashMap<>();
        PathTableNode currentRoot = null;
        Map<String, PathTableNode> currentNameToNode = null;
        if (CollectionUtils.isNotEmpty(paths)) {
            for (List<Object> path : paths) {
                if (CollectionUtils.isEmpty(path)) {
                    continue;
                }
                PathTableNode rootNode = objectToPathTableNodeDto(path.get(0), PathType.path);
                if (nameToRoot.containsKey(rootNode.getName())) {
                    checkConflict(nameToRoot.get(rootNode.getName()), rootNode);
                    currentRoot = nameToRoot.get(rootNode.getName());
                } else {
                    roots.add(rootNode);
                    nameToRoot.put(rootNode.getName(), rootNode);
                    currentRoot = rootNode;
                }
                currentNameToNode = currentRoot.getNameToChild();
                int lastIndex = path.size() - 1;
                for (int i = 1; i < lastIndex; i++) {
                    PathTableNode middle = objectToPathTableNodeDto(path.get(i), PathType.path);
                    if (currentNameToNode.containsKey(middle.getName())) {
                        checkConflict(currentNameToNode.get(middle.getName()), middle);
                        currentRoot = currentNameToNode.get(middle.getName());
                    } else {
                        currentRoot.addChild(middle);
                        currentRoot = middle;
                    }
                    currentNameToNode = currentRoot.getNameToChild();
                }
                PathTableNode last = objectToPathTableNodeDto(path.get(lastIndex), PathType.table);
                if (currentNameToNode.containsKey(last.getName())) {
                    checkConflict(currentNameToNode.get(last.getName()), last); continue;
                }
                currentRoot.addChild(last);
            }
        }

        return roots;
    }


    public static PathTableNode objectToPathTableNodeDto(Object object, PathType pathType) {
        PathTableNode dto;
        if (object instanceof String) {
            String name = (String)object;
            if (pathType == PathType.table) {
                dto = createTableNode(name);
            } else {
                dto = createPathNode(name);
            }
        } else if (object instanceof PathTableNode) {
            dto = (PathTableNode)object;
        } else {
            throw new RuntimeException("wrong path element " + object);
        }
        return dto;
    }

    public void initChildrenAndMapping(List<PathTableNode> children) {
        this.children = children;
        this.nameToChild = CollectionUtils.emptyIfNull(children).stream().collect(Collectors.toMap(PathTableNode::getName, Function.identity()));
    }

    private static void checkConflict(PathTableNode left, PathTableNode right) {
        if (left.getPathType() != right.getPathType()) {
            throw new RuntimeException("conflict path element " + left.getName());
        }
    }
}
